package com.android.compiler;

import com.android.annotation.Router;
import com.android.annotation.entity.RouterEntity;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;


@AutoService(Processor.class)
@SupportedAnnotationTypes(Const.ANNOTATION_TYPE_ROUTE)//支持注解类型
@SupportedSourceVersion(SourceVersion.RELEASE_7)//编译的版本
@SupportedOptions(Const.KEY_MODULE_NAME)
public class RouterProcessor extends AbstractProcessor {
    /**
     * 节点工具类 (类、函数、属性都是节点)
     */
    private Elements elementUtils;
    /**
     * type(类信息)工具类
     */
    private Types typesUtils;
    /**
     * 文件生成器 类/资源
     */
    private Filer filer;
    /**
     * key:组名 value:类名
     */
    private Map<String, String> AllGroupMap = new TreeMap<>();
    /**
     * 分组 key:组名 value:对应组的路由信息
     */
    private Map<String, List<RouterEntity>> groupMap = new HashMap<>();

    private Log log;

    private String moduleName;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        //获得apt的日志输出
        log = Log.newLog(processingEnv.getMessager());
        elementUtils = processingEnv.getElementUtils();
        typesUtils = processingEnv.getTypeUtils();
        filer = processingEnv.getFiler();

        //参数是模块名 为了防止多模块/组件化开发的时候 生成相同的 xx$$ROOT$$文件
        Map<String, String> options = processingEnv.getOptions();
        if (MapUtils.isNotEmpty(options)) {
            moduleName = options.get(Const.KEY_MODULE_NAME);
            if (StringUtils.isNotEmpty(moduleName)) {
                moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", "");
            } else {
                throw new RuntimeException("Not set processor moudleName option !");
            }
        }
        log.i("init RouterProcessor " + moduleName + " success !");
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (CollectionUtils.isNotEmpty(annotations)) {
            //获取所有被@Router注解的类节点
            Set<? extends Element> rootElements = roundEnv.getElementsAnnotatedWith(Router.class);
            if (CollectionUtils.isNotEmpty(rootElements)) {
                processorRouter(rootElements);
            }
            return true;
        }
        return false;
    }

    private void processorRouter(Set<? extends Element> rootElements) {
        //获得Activity这个类的节点信息
        TypeElement activity = elementUtils.getTypeElement(Const.ACTIVITY);
        TypeElement module = elementUtils.getTypeElement(Const.IMODULE);
        for (Element element : rootElements) {
            RouterEntity routerEntity;
            //类信息
            TypeMirror typeMirror = element.asType();
            log.i("Router class:" + typeMirror.toString());
            Router router = element.getAnnotation(Router.class);
            if (typesUtils.isSubtype(typeMirror, activity.asType())) {
                routerEntity = new RouterEntity(RouterEntity.Type.ACTIVITY, router, element);
            } else if (typesUtils.isSubtype(typeMirror, module.asType())) {
                routerEntity = new RouterEntity(RouterEntity.Type.IMODULE, router, element);
            } else {
                throw new RuntimeException("Just support Activity or IService Router: " + element);
            }
            categories(routerEntity);
        }
        log.i("RouteGroup impl:" + elementUtils.getTypeElement(Const.IROUTE_GROUP));
        TypeElement iRouterGroup = elementUtils.getTypeElement(Const.IROUTE_GROUP);
        TypeElement iRouterAllGroup = elementUtils.getTypeElement(Const.IROUTE_ALL_GROUP);

        //生成Group记录分组表
        generatedGroup(iRouterGroup);

        //生成Root类 作用：记录<分组，对应的Group类>
        generatedAllGroup(iRouterAllGroup, iRouterGroup);
    }

    private void generatedGroup(TypeElement iRouterGroup) {
        //javapoet创建顺序是先创建参数，然后创建方法，最后创建类
        //javapoet字符串格式化规则，$L代表字面量如int $S代表字符串 $T代表类、接口 $S代表变量

        //创建参数类型 Map<String, RouterEntity> map
        ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ClassName.get(RouterEntity.class));
        ParameterSpec map = ParameterSpec.builder(parameterizedTypeName, "map").build();

        // 创建重载方法 @Override public void loadRouter(Map<String, RouterEntity> map)
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Const.METHOD_LOAD_ROUTER)
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .addParameter(map);

        for (Map.Entry<String, List<RouterEntity>> entry : groupMap.entrySet()) {
            String groupName = entry.getKey();
            List<RouterEntity> groupData = entry.getValue();
            for (RouterEntity routerEntity : groupData) {
                methodBuilder.addStatement("map.put($S,$T.build($T.$L,$T.class,$S,$S))",
                        routerEntity.getPath(),
                        ClassName.get(RouterEntity.class),
                        ClassName.get(RouterEntity.Type.class),
                        routerEntity.getType(),
                        ClassName.get(((TypeElement) routerEntity.getElement())),
                        routerEntity.getPath(),
                        routerEntity.getGroup());
            }
            String groupClassName = Const.NAME_OF_GROUP + groupName;
            TypeSpec typeSpec = TypeSpec.classBuilder(groupClassName)
                    .addSuperinterface(ClassName.get(iRouterGroup))
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(methodBuilder.build())
                    .build();
            JavaFile javaFile = JavaFile.builder(Const.ROUTER_PACKAGE_NAME, typeSpec).build();
            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            AllGroupMap.put(groupName, groupClassName);
            log.i("AllGroupMap:" + AllGroupMap);
        }

    }

    private void generatedAllGroup(TypeElement iRouterAllGroup, TypeElement iRouteGroup) {
        //创建参数类型 Map<String,Class<? extends IRouterGroup>> routers>
        //Wildcard 通配符
        ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(
                        ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(iRouteGroup))
                ));
        //参数 Map<String,Class<? extends IRouterGroup>> routers> routers
        ParameterSpec parameter = ParameterSpec.builder(parameterizedTypeName, "routers").build();
        //函数 public void loadRouter(Map<String,Class<? extends IRouterGroup>> routers> routers)
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Const.METHOD_LOAD_ROUTER)
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .addParameter(parameter);
        //函数体
        for (Map.Entry<String, String> entry : AllGroupMap.entrySet()) {
            methodBuilder.addStatement("routers.put($S, $T.class)", entry.getKey(),
                    ClassName.get(Const.ROUTER_PACKAGE_NAME, entry.getValue()));
        }
        String className = Const.NAME_OF_ALL_GROUP + moduleName;
        TypeSpec typeSpec = TypeSpec.classBuilder(className)
                .addSuperinterface(ClassName.get(iRouterAllGroup))
                .addModifiers(Modifier.PUBLIC)
                .addMethod(methodBuilder.build())
                .build();
        try {
            JavaFile.builder(Const.ROUTER_PACKAGE_NAME, typeSpec).build().writeTo(filer);
            log.i("Generated RouteRoot：" + Const.ROUTER_PACKAGE_NAME + "." + className);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 检查是否配置 group 如果没有配置 则从path截取出组名
     */
    private void categories(RouterEntity routerEntity) {
        if (routerVerify(routerEntity)) {
            log.i("Group : " + routerEntity.getGroup() + " path=" + routerEntity.getPath());
            //分组与组中的路由信息
            List<RouterEntity> routerEntities = groupMap.get(routerEntity.getGroup());
            if (CollectionUtils.isEmpty(routerEntities)) {
                routerEntities = new ArrayList<>();
                routerEntities.add(routerEntity);
                groupMap.put(routerEntity.getGroup(), routerEntities);
            } else {
                routerEntities.add(routerEntity);
            }
        } else {
            log.i("Group info error:" + routerEntity.getPath());
        }
    }

    /**
     * 验证path路由地址的合法性
     */
    private boolean routerVerify(RouterEntity routerEntity) {
        String path = routerEntity.getPath();
        String group = routerEntity.getGroup();
        // 必须以 / 开头来指定路由地址
        if (!path.startsWith("/")) {
            return false;
        }
        //如果group没有设置 我们从path中获得group
        if (StringUtils.isEmpty(group)) {
            String defaultGroup = path.substring(1, path.indexOf("/", 1));
            //截取出的group还是空
            if (StringUtils.isEmpty(defaultGroup)) {
                return false;
            }
            routerEntity.setGroup(defaultGroup);
        }
        return true;
    }

}
